package adc.budget.ui.web.wicket.category;

import static adc.app.ui.wicket.util.WktUtil.alert;

import java.io.Serializable;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;

import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreeNode;

import org.apache.wicket.Application;
import org.apache.wicket.Component;
import org.apache.wicket.MarkupContainer;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.markup.html.AjaxLink;
import org.apache.wicket.behavior.AttributeAppender;
import org.apache.wicket.behavior.SimpleAttributeModifier;
import org.apache.wicket.extensions.markup.html.tree.table.AbstractTreeColumn;
import org.apache.wicket.extensions.markup.html.tree.table.ColumnLocation;
import org.apache.wicket.extensions.markup.html.tree.table.IColumn;
import org.apache.wicket.extensions.markup.html.tree.table.TreeTable;
import org.apache.wicket.extensions.markup.html.tree.table.ColumnLocation.Alignment;
import org.apache.wicket.extensions.markup.html.tree.table.ColumnLocation.Unit;
import org.apache.wicket.markup.html.WebComponent;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.panel.Panel;
import org.apache.wicket.model.Model;
import org.apache.wicket.model.ResourceModel;
import org.apache.wicket.spring.injection.annot.SpringBean;

import adc.app.ui.wicket.IMsgs;
import adc.budget.spec.bo.ICategory;
import adc.budget.spec.bs.ICategoryBs;
import adc.budget.ui.web.wicket.BudgetApplication;

import com.google.common.collect.Lists;
public class CategoryListPanel extends Panel {

    private static final long serialVersionUID = 1L;
    private static final String ID_TREE_TABLE = "tt";
    
    public interface IAddClickedCallback extends Serializable {
        void onAddClicked(AjaxRequestTarget target);
    }

    public interface IEditClickedCallback extends Serializable {
        void onEditClicked(AjaxRequestTarget target, ICategory category);
    }
    
    @SpringBean
    private ICategoryBs categoryBs;
    private IAddClickedCallback addClickedCallback;
    private IEditClickedCallback editClickedCallback;
    
    private final TreeTable tt;

    public CategoryListPanel(String id) {
        super(id);
        final IColumn[] cols = new IColumn[1];
        cols[0] = new CategoryTreeColumn(new ColumnLocation(Alignment.LEFT, 30, Unit.PERCENT), 
            Application.get().getResourceSettings().getLocalizer().getString(IMsgsCategory.CATEGORY, null));
        tt = new XTreeTable(ID_TREE_TABLE, new DefaultTreeModel(new CategoryNode(null)), cols);
        tt.setRootLess(true);
        add(tt);
        add(new AddNodeButton("add", "addLbl"));
        add(new EditNodeButton("edit", "editLbl"));
        add(new DeleteNodeButton("delete", "deleteLbl"));
        add(new AttributeAppender("class", true, Model.of("bgt-cat-list"), " "));
        
    }

    public IAddClickedCallback getAddClickedCallback() {
        return addClickedCallback;
    }

    public CategoryListPanel setAddClickedCallback(IAddClickedCallback addClickedCallback) {
        this.addClickedCallback = addClickedCallback;
        return this;
    }

    public IEditClickedCallback getEditClickedCallback() {
        return editClickedCallback;
    }

    public CategoryListPanel setEditClickedCallback(IEditClickedCallback editClickedCallback) {
        this.editClickedCallback = editClickedCallback;
        return this;
    }

    private void onAddClicked(AjaxRequestTarget target) {
        if (addClickedCallback != null) {
            addClickedCallback.onAddClicked(target);
        }
    }

    private void onEditClicked(AjaxRequestTarget target) {
        if (editClickedCallback != null) {
            if (!tt.getTreeState().getSelectedNodes().isEmpty()) {
                CategoryNode catNode = (CategoryNode) tt.getTreeState().getSelectedNodes().iterator().next();
                
                editClickedCallback.onEditClicked(target, categoryBs.byId(catNode.id));
            } else {
                alert(target, getString(IMsgsCategory.ERR_SELECTION_REQUIRED_ONE));
            }
        }
    }

    public void save(AjaxRequestTarget target, ICategory cat) {
        final DefaultTreeModel tm = (DefaultTreeModel) tt.getModel().getObject();
        categoryBs.save(cat);
        ((CategoryNode) tm.getRoot()).reload();
        tm.reload();
        tt.updateTree(target);
    }

    private void onDeleteClicked(AjaxRequestTarget target) {
        if (!tt.getTreeState().getSelectedNodes().isEmpty()) {
            final CategoryNode catNode = (CategoryNode) tt.getTreeState().getSelectedNodes().iterator().next();
            final ICategory cat = categoryBs.byId(catNode.id);
            categoryBs.delete(cat);
            final DefaultTreeModel tm = (DefaultTreeModel) tt.getModel().getObject();
            final int catNodeIdx = catNode.getParent().getIndex(catNode);
            tt.getTreeState().selectNode(catNode, false);
            catNode.parent.removeChild(catNode);
            tm.nodesWereRemoved(catNode.getParent(), new int[]{catNodeIdx}, 
                    new Object[]{catNode});
            tt.updateTree(target);
        } else {
            alert(target, getString(IMsgsCategory.ERR_SELECTION_REQUIRED_ONE));
        }
    }

    private static final class CategoryNode implements TreeNode, Serializable {

        private static final long serialVersionUID = 1L;

        private List<CategoryNode> children;
        private Enumeration<CategoryNode> childrenEnum;
        private int childCount;
        private CategoryNode parent;
        private boolean leaf;
        private boolean leafLoaded;
        private boolean childrenLoaded;
        private final Long id;
        private final String name;
        private final String notes;
        private final Boolean income;

        public CategoryNode(final ICategory cat) {
            if (cat != null) {
                this.id = cat.getId();
                this.name = cat.getName();
                this.income  = cat.getIncome();
                this.notes = cat.getNotes();
            } else {
                this.id = null;
                this.name = null;
                this.income  = null;
                this.notes = null;
            }
        }

        public void reload() {
            childrenLoaded = false;
            leafLoaded = false;
        }

        public void removeChild(CategoryNode catNode) {
            children.remove(catNode);
            childCount = children.size();
            leaf = childCount < 1;
        }

        @SuppressWarnings("unused")
        public Boolean getIncome() {
            return income;
        }

        @SuppressWarnings("unused")
        public String getName() {
            return name;
        }

        @SuppressWarnings("unused")
        public String getNotes() {
            return notes;
        }

        @Override
        public Enumeration<CategoryNode> children() {
            if (!childrenLoaded) {
                childrenEnum = Collections.enumeration(childrenInternal());
            }
            return childrenEnum;
        }

        @Override
        public boolean getAllowsChildren() {
            return true;
        }

        @Override
        public TreeNode getChildAt(int childIndex) {
            return childrenInternal().get(childIndex);
        }

        @Override
        public int getChildCount() {
            if (!childrenLoaded) {
                childCount = childrenInternal().size();
            }
            return childCount;
        }

        @Override
        public int getIndex(TreeNode node) {
            return childrenInternal().indexOf(node);
        }

        @Override
        public TreeNode getParent() {
            return parent;
        }

        @Override
        public boolean isLeaf() {
            if (!leafLoaded) {
                final ICategoryBs catBs = BudgetApplication.get().getCategoryBs(); 
                leaf = catBs.count(Collections.singletonMap("parent", (Object) catBs.byId(id))) < 1;
                leafLoaded = true;
            }
            return leaf;
        }

        private List<CategoryNode> childrenInternal() {
            if (!childrenLoaded) {
                final ICategoryBs catBs = BudgetApplication.get().getCategoryBs();
                children = Lists.newArrayList();
                for (ICategory cat : catBs.list(Collections.singletonMap("parent", (Object) catBs.byId(id)), null, null, null)) {
                    final CategoryNode child = new CategoryNode(cat);
                    child.parent = this;
                    children.add(child);
                }
                childrenLoaded = true;
            }
            return children;
        }
    }
    
    private static final class AddNodeButton extends AjaxLink<Object> {

        private static final long serialVersionUID = 1L;

        public AddNodeButton(String id, String labelId) {
            super(id);
            add(new Label(labelId, new ResourceModel(IMsgs.ADD)));
        }

        @Override
        public void onClick(AjaxRequestTarget target) {
            this.findParent(CategoryListPanel.class).onAddClicked(target);
        }
    }

    private static final class EditNodeButton extends AjaxLink<Object>  {

        private static final long serialVersionUID = 1L;

        public EditNodeButton(String id, String labelId) {
            super(id);
            add(new Label(labelId, new ResourceModel(IMsgs.EDIT)));
        }

        @Override
        public void onClick(AjaxRequestTarget target) {
            this.findParent(CategoryListPanel.class).onEditClicked(target);
        }
    }

    private static final class DeleteNodeButton extends AjaxLink<Object>  {

        private static final long serialVersionUID = 1L;

        public DeleteNodeButton(String id, String labelId) {
            super(id);
            add(new Label(labelId, new ResourceModel(IMsgs.DELETE)));
        }

        @Override
        public void onClick(AjaxRequestTarget target) {
            this.findParent(CategoryListPanel.class).onDeleteClicked(target);
        }
    }

    private static final class XTreeTable extends TreeTable {

        private static final long serialVersionUID = 1L;

        public XTreeTable(String id, TreeModel model, IColumn[] columns) {
            super(id, model, columns);
        }

        @Override
        protected Component newNodeIcon(MarkupContainer parent, String id, final TreeNode node) {
            return new WebComponent(id).setVisible(false);
        }

    }

    private static final class CategoryTreeColumn extends AbstractTreeColumn {

        private static final long serialVersionUID = 1L;

        public CategoryTreeColumn(ColumnLocation location, String header) {
            super(location, header);
        }

        @Override
        public String renderNode(TreeNode node) {
            return ((CategoryNode) node).name;
        }

        @Override
        public Component newCell(MarkupContainer parent, String id, TreeNode node, int level) {
            final Component comp = super.newCell(parent, id, node, level);
            final CategoryNode catNode = (CategoryNode) node;
            if (catNode.notes != null) {
                comp.add(new SimpleAttributeModifier("title", ((CategoryNode) node).notes));
            }
            if (!Boolean.TRUE.equals(catNode.income)) {
                comp.add(new AttributeAppender("class", true, Model.of("bgt-expense"), " "));
            }
            return comp;
        }

        
    }
}

